package com.spzx.gateway.handler;

import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.spzx.common.core.utils.ServletUtils;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
import reactor.core.publisher.Mono;

/**
 * 自定义限流异常处理类，用于处理由Sentinel限流机制触发的异常。
 * 当请求被限流时，该处理器会返回一个友好的提示信息给客户端。
 *
 * @author spzx
 */
public class SentinelFallbackHandler implements WebExceptionHandler
{
    /**
     * 写入响应内容到客户端。
     *
     * @param response 服务器响应对象，包含HTTP状态码、头信息和响应体。
     * @param exchange 服务器Web交换对象，封装了HTTP请求和响应的所有信息。
     * @return 返回一个无返回值的Mono对象，表示异步操作完成。
     */
    private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange)
    {
        // 使用ServletUtils工具类将响应内容写入到exchange中，并设置提示信息
        return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "请求超过最大数，请稍候再试");
    }

    /**
     * 处理服务器Web交换中的异常。
     *
     * @param exchange 服务器Web交换对象，封装了HTTP请求和响应的所有信息。
     * @param ex       异常对象，表示捕获到的异常。
     * @return 返回一个无返回值的Mono对象，表示异步操作完成。
     */
    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex)
    {
        // 如果响应已经被提交，则直接返回错误，避免重复响应
        if (exchange.getResponse().isCommitted())
        {
            return Mono.error(ex);
        }

        // 检查异常是否是由限流机制触发的BlockException
        if (!BlockException.isBlockException(ex))
        {
            // 如果不是限流异常，则直接返回原始异常
            return Mono.error(ex);
        }

        // 如果是限流异常，则调用handleBlockedRequest方法处理限流请求，并将结果写入响应
        return handleBlockedRequest(exchange, ex).flatMap(response -> writeResponse(response, exchange));
    }

    /**
     * 处理限流请求。
     *
     * @param exchange 服务器Web交换对象，封装了HTTP请求和响应的所有信息。
     * @param throwable 异常对象，表示捕获到的异常。
     * @return 返回一个包含ServerResponse的Mono对象，表示对限流请求的处理结果。
     */
    private Mono<ServerResponse> handleBlockedRequest(ServerWebExchange exchange, Throwable throwable)
    {
        // 调用GatewayCallbackManager中的BlockHandler来处理限流请求
        return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable);
    }
}
